#include "stdafx.h"

#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

#include "torrent/MetaInfo.hpp"
#include "torrent/Bdecoder.hpp"
#include "torrent/TrackerClient.hpp"
#include "bencode/bencode.h"
#include "torrent/bencode_helper.hpp"
#include "bencode/Bdecoder.hpp"
#include "torrent/PeerWire.hpp"
#include "torrent/Client.hpp"
#include "torrent/Tracker.hpp"
#include "torrent/Manager.hpp"

//#include "testTracker.hpp"

#if defined(_WIN32) && defined(_DEBUG)
#define new MYDEBUG_NEW
#endif

void testBdecoder();
void testManager();
void testSyncClient();
bool testPeerClient(torrent::Peer_t peer, std::string& info_hash);
void testBencodeHelper(const std::string& filePath);
void testifs();
void testUpnp();

//////////////////////////////////////////////////////////////////////////
/// upnp feature /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

#include "miniupnpc.h"
#include "upnpcommands.h"
#include "upnperrors.h"

static void SetRedirectAndTest(struct UPNPUrls * urls,
                               struct IGDdatas * data,
							   const char * iaddr,
							   const char * iport,
							   const char * eport,
                               const char * proto,
                               const char * leaseDuration);

static void
RemoveRedirect(struct UPNPUrls * urls,
               struct IGDdatas * data,
			   const char * eport,
			   const char * proto);

//////////////////////////////////////////////////////////////////////////

int main(int /*argc*/, char* /*argv*/[])
{
	testManager();
	//testSyncClient();
	//testUpnp();
#ifdef _WIN32
	_CrtDumpMemoryLeaks();
#endif
	return 0;
}


boost::function0<void> console_ctrl_function;

BOOL WINAPI console_ctrl_handler(DWORD ctrl_type)
{
	switch (ctrl_type)
	{
	case CTRL_C_EVENT:
	case CTRL_BREAK_EVENT:
	case CTRL_CLOSE_EVENT:
	case CTRL_SHUTDOWN_EVENT:
		console_ctrl_function();
		return TRUE;
	default:
		return FALSE;
	}
}

void testManager()
{
	torrent::Manager manager(64518);
	manager.getClient()._workingPath = "./temp/";
	manager.getClient()._storagePath = "./data/";
	//manager.openTorrent("test.torrent");
	//manager.openTorrent("test2.torrent");
	//manager.openTorrent("test3.torrent");
	//manager.openTorrent("test4.torrent");	// udp tracker
	//manager.openTorrent("test5.torrent");
	//manager.openTorrent("test6.torrent");
	//manager.openTorrent("test_multi_file.torrent");
	//manager.openTorrent("test.hot.torrent");
	manager.openTorrent("test.up.torrent");

	SetConsoleCtrlHandler(console_ctrl_handler, true);
	console_ctrl_function = boost::bind(&torrent::Manager::stop, &manager);

	manager.run();
}

void testUpnp()
{
	char lanaddr[64];	/* my ip address on the LAN */
	const char * multicastif = 0;
	const char * minissdpdpath = 0;
	int error = 0;
	int ipv6 = 0;

	struct UPNPDev * devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0/*sameport*/, ipv6, &error);

	if (devlist)
	{
		struct UPNPUrls urls;
		struct IGDdatas data;
		int ret = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
		if (ret)
		{
			SetRedirectAndTest(&urls, &data, lanaddr, "6881", "6881", "TCP", "0");
			RemoveRedirect(&urls, &data, "6881", "TCP");
		}
		FreeUPNPUrls(&urls);
		freeUPNPDevlist(devlist);
		devlist = 0;
	}
	else
		std::cout << strupnperror(error);
}

//////////////////////////////////////////////////////////////////////////
/// upnp feature /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

/* protofix() checks if protocol is "UDP" or "TCP" 
 * returns NULL if not */
const char * protofix(const char * proto)
{
	static const char proto_tcp[4] = { 'T', 'C', 'P', 0};
	static const char proto_udp[4] = { 'U', 'D', 'P', 0};
	int i, b;
	for(i=0, b=1; i<4; i++)
		b = b && (   (proto[i] == proto_tcp[i]) 
		          || (proto[i] == (proto_tcp[i] | 32)) );
	if(b)
		return proto_tcp;
	for(i=0, b=1; i<4; i++)
		b = b && (   (proto[i] == proto_udp[i])
		          || (proto[i] == (proto_udp[i] | 32)) );
	if(b)
		return proto_udp;
	return 0;
}

/* Test function 
 * 1 - get connection type
 * 2 - get extenal ip address
 * 3 - Add port mapping
 * 4 - get this port mapping from the IGD */
static void SetRedirectAndTest(struct UPNPUrls * urls,
                               struct IGDdatas * data,
							   const char * iaddr,
							   const char * iport,
							   const char * eport,
                               const char * proto,
                               const char * leaseDuration)
{
	char externalIPAddress[40];
	char intClient[40];
	char intPort[6];
	char duration[16];
	int r;

	if(!iaddr || !iport || !eport || !proto)
	{
		fprintf(stderr, "Wrong arguments\n");
		return;
	}
	proto = protofix(proto);
	if(!proto)
	{
		fprintf(stderr, "invalid protocol\n");
		return;
	}
	
	UPNP_GetExternalIPAddress(urls->controlURL,
	                          data->first.servicetype,
							  externalIPAddress);
	if(externalIPAddress[0])
		printf("ExternalIPAddress = %s\n", externalIPAddress);
	else
		printf("GetExternalIPAddress failed.\n");
	
	r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype,
	                        eport, iport, iaddr, 0, proto, 0, leaseDuration);
	if(r!=UPNPCOMMAND_SUCCESS)
		printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n",
		       eport, iport, iaddr, r, strupnperror(r));

	r = UPNP_GetSpecificPortMappingEntry(urls->controlURL,
	                                 data->first.servicetype,
    	                             eport, proto,
									 intClient, intPort, NULL/*desc*/,
	                                 NULL/*enabled*/, duration);
	if(r!=UPNPCOMMAND_SUCCESS)
		printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n",
		       r, strupnperror(r));
	
	if(intClient[0]) {
		printf("InternalIP:Port = %s:%s\n", intClient, intPort);
		printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n",
		       externalIPAddress, eport, proto, intClient, intPort, duration);
	}
}

static void
RemoveRedirect(struct UPNPUrls * urls,
               struct IGDdatas * data,
			   const char * eport,
			   const char * proto)
{
	int r;
	if(!proto || !eport)
	{
		fprintf(stderr, "invalid arguments\n");
		return;
	}
	proto = protofix(proto);
	if(!proto)
	{
		fprintf(stderr, "protocol invalid\n");
		return;
	}
	r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, 0);
	printf("UPNP_DeletePortMapping() returned : %d\n", r);
}
